package info.himanshug.www.picfun;

import info.himanshug.www.imageprocessing.PencilSketchEffect;


import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;

import com.bala.R;
import android.app.Activity;
import android.app.ProgressDialog;
import android.content.Context;
import android.content.Intent;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Bitmap.Config;
import android.net.Uri;
import android.util.Log;
import android.view.KeyEvent;
import android.view.SurfaceHolder;
import android.widget.Toast;

public class ImageViewRenderThread extends Thread {

	private Bitmap bMap;
	private Bitmap workingBMap;
	private Bitmap bScaledMap;
	private int[] pixels;
	private int[] workingPixels;

	private int mImgStartX;
	private int mImgStartY;
	
	private Context mContext;
	
	private PencilSketchEffect pse;
	
	private Resources res;
	
	/**
	 * Current height of the surface/canvas.
	 * 
	 * @see #setSurfaceSize
	 */
	int mCanvasHeight = 1;

	/**
	 * Current width of the surface/canvas.
	 * 
	 * @see #setSurfaceSize
	 */
	int mCanvasWidth = 1;

	/** Handle to the surface manager object we interact with */
	private SurfaceHolder mSurfaceHolder;
	private boolean mRun;
	private boolean stateChanged = true;
	private String imagePath;


	private Activity activity;
	
	private int staticDisplayW;
	private int staticDisplayH;
	
	public ImageViewRenderThread(SurfaceHolder surfaceHolder, Context context) {
		Log.d(Constants.APP_NAME,"creating brand new render thread");
		// get handles to some important objects
		mSurfaceHolder = surfaceHolder;
		mContext = context;
	}

	public ImageViewRenderThread(ImageViewRenderThread other) {
		// get handles to some important objects
		mSurfaceHolder = other.mSurfaceHolder;
		mContext = other.mContext;
		bMap = other.bMap;
		workingBMap = other.workingBMap;
		bScaledMap = other.bScaledMap;
		pixels = other.pixels;
		workingPixels = other.workingPixels;
		mCanvasHeight = other.mCanvasHeight;
		mCanvasWidth = other.mCanvasWidth;
		staticDisplayW = other.staticDisplayW;
		staticDisplayH = other.staticDisplayH;
		mImgStartX = other.mImgStartX;
		mImgStartY = other.mImgStartY;
		pse = other.pse;
		imagePath = other.imagePath;
		res = other.res;
	}

	public void openImage(String imageName, int radius) {
		this.imagePath = imageName;
		// Resources res = mContext.getResources();
		FileInputStream in;
		BufferedInputStream buf;
		try {
			int MAX_FILE_SIZE = 4 * 1024 * 1024;
			if(new File(imageName).length() >= MAX_FILE_SIZE) {
				Toast.makeText(mContext, "File size is higher than 4MB.", Toast.LENGTH_LONG).show();
				return;
			}
			//Correct this resampling code and take care of the part where
			//you could not get any image
//			if (buf.available() > 1000 * 1024) {
//				BitmapFactory.Options opts = new BitmapFactory.Options();
//				opts.inSampleSize = (int) Math.sqrt(buf.available()
//						/ (10 * 1024.0));
//				bMap = BitmapFactory.decodeStream(buf, null, opts);
//				Toast.makeText(mContext,
//						"Resampled the image to fit memory",
//						Toast.LENGTH_LONG).show();
//			} else {
//				bMap = BitmapFactory.decodeStream(buf);
//			}
			
			in = new FileInputStream(imageName);
			buf = new BufferedInputStream(in);
			
			if(buf.available() > 500 * 1024) {
				BitmapFactory.Options opts = new BitmapFactory.Options();
				opts.inSampleSize = (int) Math.sqrt(buf.available()
						/ (22.36 * 1024.0));
				bMap = BitmapFactory.decodeStream(buf, null, opts);
//				Toast.makeText(mContext, "Resampled the image to fit memory",
//						Toast.LENGTH_LONG).show();
			}
			else
				bMap = BitmapFactory.decodeStream(buf);
			//scale the bit map if necessary
			int MAX_SIDE = 800;
			int w = bMap.getWidth();
			int h = bMap.getHeight();
			if(w > MAX_SIDE || h > MAX_SIDE) {
				int newW, newH = -1;
				if(w > h) {
					newW = MAX_SIDE;
					newH = (int)((float)MAX_SIDE * h)/w;
					bMap = Bitmap.createScaledBitmap(bMap, newW, newH, true);
				}
				else {
					newH = MAX_SIDE;
					newW = (int)((float)MAX_SIDE * w)/h;
					bMap = Bitmap.createScaledBitmap(bMap, newW, newH, true);
				}
				
//				Toast.makeText(mContext,
//						"Rescaled the image to " + newW + " X " + newH + " px",Toast.LENGTH_LONG).show();
				
			}
			//Convert to ARGB_8888
			bMap = bMap.copy(Config.ARGB_8888, true);

			Log.d(Constants.APP_NAME,"Opening the foto with width/height being " + staticDisplayW + " and " + staticDisplayH);
			//open the image with desired effect
			pse = new PencilSketchEffect(bMap,staticDisplayW,staticDisplayH);
			Log.d(Constants.APP_NAME,"applying sketch effect...");
			if(radius > 0)
				workingBMap = pse.getSketchBitmap(radius);
			else
				workingBMap = pse.getSketchBitmap(PencilSketchEffect.DEFAULT_RADIUS);
			
			Log.d(Constants.APP_NAME,"applied sketch effect...");
			pixels = new int[bMap.getWidth() * bMap.getHeight()];
			
			//TODO: probably we don't need any of this for this
			//case
			bMap.getPixels(pixels, 0, bMap.getWidth(), 0, 0, bMap
					.getWidth(), bMap.getHeight());

			workingPixels = new int[workingBMap.getWidth()
			                        * workingBMap.getHeight()];
			workingBMap.getPixels(workingPixels, 0, workingBMap
					.getWidth(), 0, 0, workingBMap.getWidth(),
			workingBMap.getHeight());
			
			if (in != null) {
				in.close();
			}
			if (buf != null) {
				buf.close();
			}

			mImgStartX = (mCanvasWidth - workingBMap.getWidth()) / 2;
			mImgStartY = (mCanvasHeight - workingBMap.getHeight()) / 2;
			
			//this.notify();

		} catch (Throwable e) {
			Toast.makeText(mContext,
					res.getString(R.string.errInternal),Toast.LENGTH_LONG).show();
			Log.e(Constants.APP_NAME, "Error reading file", e);
		}
	}

	public void updateImage(int radius) {
		Log.d(Constants.APP_NAME,"updating the image...");
		final ProgressDialog pd = new ProgressDialog(mContext);
		pd.setCancelable(false);
		pd.setMessage(res.getString(R.string.updating));
		
		pd.show();
		final int r = radius;
		Thread t = new Thread() {
			public  void run() {
				try {
					synchronized (mSurfaceHolder) {
						workingBMap = pse.getSketchBitmap(r);
						pixels = new int[bMap.getWidth() * bMap.getHeight()];
						bMap.getPixels(pixels, 0, bMap.getWidth(), 0, 0, bMap
								.getWidth(), bMap.getHeight());
				
						workingPixels = new int[workingBMap.getWidth()
						                        * workingBMap.getHeight()];
						workingBMap.getPixels(workingPixels, 0, workingBMap
								.getWidth(), 0, 0, workingBMap.getWidth(),
						workingBMap.getHeight());
						mImgStartX = (mCanvasWidth - workingBMap.getWidth()) / 2;
						mImgStartY = (mCanvasHeight - workingBMap.getHeight()) / 2;
					}
					Log.d(Constants.APP_NAME,"updated the image...");
				} catch(Throwable ex) {
					Log.e(Constants.APP_NAME,"Error occured in updating image",ex);
				} finally { pd.dismiss(); }
			}
		};
		//activity.runOnUiThread(t);
		t.start();
	}
	
	public void save(final boolean shouldShare) {
		final ProgressDialog pd = new ProgressDialog(mContext);
		pd.setCancelable(false);
		pd.setMessage(res.getString(R.string.saving));
		pd.show();
		Thread t = new Thread() {
			public void run() {
				FileOutputStream out = null;
				try {
					Log.d(Constants.APP_NAME,"Saving the image...");
					Bitmap bitmapToSave = null;
					synchronized (mSurfaceHolder) {
						bitmapToSave =pse.getSketchBitmapFromOriginal();
					}
					Log.d(Constants.APP_NAME,"Compressing and Saving the image....");
					
					String newName = getNewName(imagePath);
					out = new FileOutputStream(newName);
					bitmapToSave.compress(Bitmap.CompressFormat.PNG, 90, out);
					Log.d(Constants.APP_NAME,"Saved the image successfully...");
					
					if(shouldShare) {
						Intent sharingIntent = new Intent(Intent.ACTION_SEND);
						Uri screenshotUri = Uri.parse(newName);
						sharingIntent.setType("image/png");
						sharingIntent.putExtra(Intent.EXTRA_STREAM, screenshotUri);
						activity.startActivity(Intent.createChooser(sharingIntent, "via fotolab"));
					}
				} catch (Throwable e) {
					Log.e(Constants.APP_NAME,"Error in saving the image",e);
				} finally {
					try {
						if(out != null) out.close();
					}
					catch(Throwable e) {
						Log.e(Constants.APP_NAME,"Error in closing output stream while saving the image",e);
					}
					pd.dismiss(); 
				}
			}
		};
		//activity.runOnUiThread(t);
		t.start();
	}
	
	private String getNewName(String originalName) {
		int MAX_ATTEMPTS = 10;
		int attempts = 0;
		
		while(attempts++ < MAX_ATTEMPTS) {
			String picName = originalName.substring(originalName.lastIndexOf("/")+1);
			//remove the extension such as .jpg, .png etc
			picName = picName.replaceAll("\\..*", "");
			
			//take only first 5 or less chars
			picName = picName.substring(0, picName.length() > 5 ? 5 : picName.length());
			
			picName = Constants.SAVED_IMAGE_FOLDER + picName + System.currentTimeMillis() + ".png";
			
			File file = new File(picName);
			if(!file.exists())
				return picName;
		}
		
		throw new RuntimeException("couldn't get a name");
	}
	
	@Override
	public void run() {
		while (mRun) {
			Canvas c = null;
			try {
				c = mSurfaceHolder.lockCanvas(null);
				synchronized (mSurfaceHolder) {
					if (stateChanged) {

						gameRender(c);
					}
				}
				
//				synchronized(this) {
//					this.wait();
//				}
				
//				if(toastToShow != null) {
//					Toast.makeText(mContext,toastToShow,Toast.LENGTH_LONG).show();
//					toastToShow = null;
//				}
			}
			catch(Throwable ex) {
				Log.e(Constants.APP_NAME, "Exception occured in updating canvas",ex);
			} finally {
				// do this in a finally so that if an exception is thrown
				// during the above, we don't leave the Surface in an
				// inconsistent state
				if (c != null) {
					mSurfaceHolder.unlockCanvasAndPost(c);
				}
			}
			try {
				Thread.sleep(100);
			} catch (InterruptedException e) {
				Log.e(Constants.APP_NAME,"error in thread",e);
			}
		}
	}

	/**
	 * Used to signal the thread whether it should be running or not.
	 * Passing true allows the thread to run; passing false will shut it
	 * down if it's already running. Calling start() after this was most
	 * recently called with false will result in an immediate shutdown.
	 * 
	 * @param b
	 *            true to run, false to shut down
	 */
	public void setRunning(boolean b) {
		mRun = b;
	}

	/**
	 * Sets the game mode. That is, whether we are running, paused, in the
	 * failure state, in the victory state, etc.
	 * 
	 * @param mode
	 *            one of the STATE_* constants
	 * @param message
	 *            string to add to screen or null
	 */

	/* Callback invoked when the surface dimensions change. */
	public void setSurfaceSize(int width, int height) {
		// synchronized to make sure these all change atomically
		synchronized (mSurfaceHolder) {
			mCanvasWidth = width;
			mCanvasHeight = height;

			if (null != workingBMap) {
				mImgStartX = (mCanvasWidth - workingBMap.getWidth()) / 2;
				mImgStartY = (mCanvasHeight - workingBMap.getHeight()) / 2;
			}
		}
	}

	/**
	 * Handles a key-down event.
	 * 
	 * @param keyCode
	 *            the key that was pressed
	 * @param msg
	 *            the original event object
	 * @return true
	 */
	boolean doKeyDown(int keyCode, KeyEvent msg) {

		return false;
	}

	/**
	 * Handles a key-up event.
	 * 
	 * @param keyCode
	 *            the key that was pressed
	 * @param msg
	 *            the original event object
	 * @return true if the key was handled and consumed, or else false
	 */
	boolean doKeyUp(int keyCode, KeyEvent msg) {
		return false;
	}

	/**
	 * Draws the cart, energy/speed bars, and background to the provided
	 * Canvas.
	 */
	private void gameRender(Canvas canvas) {
		//Log.v(Constants.APP_NAME,"rendering working image on canvas...");
		if (null == workingBMap)
			return;
		// canvas.save();
		// Draw the background image. Operations on the Canvas accumulate
		// so this is like clearing the screen.
//		workingBMap = Bitmap.createBitmap(workingPixels, workingBMap
//				.getWidth(), workingBMap.getHeight(), Config.ARGB_8888);
		canvas.drawColor(Color.BLACK);
		canvas.drawBitmap(workingBMap, mImgStartX, mImgStartY, null);

		// canvas.restore();
	}
	
	public void setActivity(Activity activity) {
		this.activity = activity;
	}
	
	public void setDisplayWidthHeight(int width, int height) {
		Log.d(Constants.APP_NAME,"setting static width/height to be " + width + " and " + height);
		this.staticDisplayH = height;
		this.staticDisplayW = width;
	}
	
	public void setResources(Resources res) { this.res = res; }
}